using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using DslCrossModelFramework.Environment;
using DslCrossModelFramework.ModelTraversal;
using Microsoft.VisualStudio.Modeling;

namespace DslCrossModelFramework.ModelRelationship {
    public class ModelRelationshipManager {

        private static ModelRelationshipManager currentManager;
        private static object syncObject = new object();
        public static ModelRelationshipManager Current {
            get {
                if (currentManager == null) {
                    lock (syncObject) {
                        if (currentManager == null) {
                            currentManager = new ModelRelationshipManager();
                        }
                    }
                }
                return currentManager;
            }
        }

        public ModelRelationshipManager() {
        }

        public void RegisterFilter(IModelElementFilter filter) {
            globalModelElementFilters.Add(filter);
        }

        public void RegisterFilter(IModelElementFilter filter, string scope) {
            if(string.IsNullOrEmpty(scope)) {
                RegisterFilter(filter);
                return;
            }
            if(!scopedModelElementFilters.ContainsKey(scope)) {
                scopedModelElementFilters.Add(scope, new ModelElementFilterCollection());
            }
            scopedModelElementFilters[scope].Add(filter);
        }

        public void RegisterRelationship(IModelRelationshipDefinition relationshipDefinition) {
            bool alreadyDefined = false;
            foreach (IModelRelationshipDefinition definition in globalModelRelationships) {
                if(definition.GetType() == relationshipDefinition.GetType()) {
                    alreadyDefined = true;
                }
            }
            if(!alreadyDefined) {
                globalModelRelationships.Add(relationshipDefinition);
            }
        }

        public void RegisterRelationship(IModelRelationshipDefinition relationshipDefinition, string scope) {
            if (string.IsNullOrEmpty(scope)) {
                RegisterRelationship(relationshipDefinition);
                return;
            }
            if(!scopedModelRelationships.ContainsKey(scope)) {
                scopedModelRelationships.Add(scope, new List<IModelRelationshipDefinition>());
            }
            bool alreadyDefined = false;
            foreach (IModelRelationshipDefinition definition in scopedModelRelationships[scope]) {
                if (definition.GetType() == relationshipDefinition.GetType()) {
                    alreadyDefined = true;
                }
            }
            if (!alreadyDefined) {
                scopedModelRelationships[scope].Add(relationshipDefinition);
            }
        }




        private List<IModelRelationshipDefinition> globalModelRelationships = new List<IModelRelationshipDefinition>();
        private Dictionary<string, List<IModelRelationshipDefinition>> scopedModelRelationships = new Dictionary<string, List<IModelRelationshipDefinition>>();

        private ModelElementFilterCollection globalModelElementFilters = new ModelElementFilterCollection();
        private Dictionary<string, ModelElementFilterCollection> scopedModelElementFilters = new Dictionary<string, ModelElementFilterCollection>();

        public List<IModelRelationshipDefinition> GlobalModelRelationships {
            get { return globalModelRelationships; }
        }
        public Dictionary<string, List<IModelRelationshipDefinition>> ScopedModelRelationships {
            get { return scopedModelRelationships; }
        }

        public ModelElementFilterCollection GlobalFilters {
            get { return globalModelElementFilters; }
        }
        public Dictionary<string, ModelElementFilterCollection> ScopedFilters {
            get { return scopedModelElementFilters; }
        }


        public List<IModelRelationshipDefinition> GetRelationships(DomainClassInfo domainClassInfo) {
            return GetRelationships(domainClassInfo, string.Empty);
        }

        public List<IModelRelationshipDefinition> GetRelationships(DomainClassInfo domainClassInfo, string scope) {
            List<IModelRelationshipDefinition> relationships = new List<IModelRelationshipDefinition>();
            if(scope == string.Empty) {
                foreach(IModelRelationshipDefinition definition in globalModelRelationships) {
                    if(ModelRelationshipMatchesClassInfo(domainClassInfo, definition)) {
                        relationships.Add(definition);
                    }
                }
            }
            else {
                if(scopedModelRelationships.ContainsKey(scope)) {
                    foreach (IModelRelationshipDefinition definition in scopedModelRelationships[scope]) {
                        if (ModelRelationshipMatchesClassInfo(domainClassInfo, definition)) {
                            relationships.Add(definition);
                        }
                    }
                }
            }


            if(relationships.Count == 0) {

                // We have a model element so return an embedded relationship type
                relationships.Add(new EmbeddedObjectModelRelationshipDefinition(EnvironmentConfiguration.Current.GetModelDirectory().GetModelElementRootType(domainClassInfo).ImplementationClass));

            }
            return relationships;
        }

        private bool ModelRelationshipMatchesClassInfo(DomainClassInfo domainClassInfo, IModelRelationshipDefinition relationship) {
            return domainClassInfo.ImplementationClass == relationship.ParentType ||
                   domainClassInfo.ImplementationClass.IsSubclassOf(relationship.ParentType);
        }
    }

    public class ModelRelationshipAlreadyDefinedException : Exception {
        public ModelRelationshipAlreadyDefinedException(Type modelType) : base(string.Format(DslCrossModelFramework.Resources.Errors.RelationshipIsAlreadyDefined, modelType.FullName)) {}
        public ModelRelationshipAlreadyDefinedException(Type modelType, string scope) : base(string.Format(DslCrossModelFramework.Resources.Errors.ARelationshipIsAlreadyDefinedForTheParent, modelType.FullName, scope)) { }
    }

    public class ModelRelationshipException : Exception {
        public ModelRelationshipException(string message) : base(message) { }
        public ModelRelationshipException(string message, Exception inner) : base(message, inner) { }
    }
}